package com.guavamail.smtp;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.guavamail.smtp.auth.AuthenticationHandler;
import com.guavamail.smtp.auth.PluginAuthenticationHandler;
import com.guavamail.smtp.command.CommandHandler;

public class SMTPServer {

	private static Logger log = LoggerFactory.getLogger(SMTPServer.class);
	/**
	 * default to all interfaces
	 */
	private InetAddress bindAddress = null;

	/**
	 * default to 25
	 */
	private int port = 25;

	private SMTPServerConfig config = new SMTPServerConfig();
	private CommandHandler commandHandler;
	private SMTPConnectionHandler handler;
	private AuthenticationHandler authenticationHandler;

	private SocketAcceptor acceptor;
	private ExecutorService executor;
	private SMTPCodecFactory codecFactory;

	private boolean running = false;
	private boolean shutdowned = false;

	public SMTPServer() {
		initInstance();
	}

	private void initInstance() {
		this.commandHandler = new CommandHandler();
		this.authenticationHandler = new PluginAuthenticationHandler();
		initService();
	}

	private void initService() {
		try {
			IoBuffer.setUseDirectBuffer(false);
			acceptor = new NioSocketAcceptor(Runtime.getRuntime().availableProcessors() + 1);

			acceptor.getSessionConfig().setReuseAddress(true);
			DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();

			codecFactory = new SMTPCodecFactory(config);
			chain.addLast("codec", new ProtocolCodecFilter(codecFactory));

			executor = Executors.newCachedThreadPool(new ThreadFactory() {
				int sequence;

				public Thread newThread(Runnable r) {
					sequence += 1;
					return new Thread(r, "SubEthaSMTP Thread " + sequence);
				}
			});

			chain.addLast("threadPool", new ExecutorFilter(executor));

			handler = new SMTPConnectionHandler(getConfig(), getCommandHandler(), authenticationHandler);

		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Call this method to get things rolling after instantiating the
	 * SMTPServer.
	 */
	public synchronized void start() {
		if (running) {
			log.info("SMTP server is already started.");
			return;
		}

		if (shutdowned)
			throw new RuntimeException("Error: server has been shutdown previously");

		InetSocketAddress isa;

		if (this.bindAddress == null) {
			isa = new InetSocketAddress(this.port);
		} else {
			isa = new InetSocketAddress(this.bindAddress, this.port);
		}

		acceptor.setBacklog(config.getBacklog());
		acceptor.setHandler(handler);

		try {
			acceptor.bind(isa);
			running = true;
			log.info("SMTP server started ...");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Stops the server by unbinding server socket. To really clean things out,
	 * one must call {@link #shutdown()}.
	 */
	public synchronized void stop() {
		try {
			acceptor.unbind();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			running = false;
		}

		log.info("SMTP server stopped.");
	}

	/**
	 * Shut things down gracefully. Please pay attention to the fact that a
	 * shutdown implies that the server would fail to restart because som
	 * internal resources have been freed.
	 * 
	 * You can directly call shutdown() if you do not intend to restart it
	 * later. Calling start() after shutdown() will throw a
	 * {@link RuntimeException}.
	 */
	public synchronized void shutdown() {
		try {
			log.info("SMTP server shutting down...");
			if (isRunning())
				stop();

			try {
				executor.shutdown();
			} catch (Exception e) {
				e.printStackTrace();
			}

			shutdowned = true;
			log.info("SMTP server shutdown complete.");
		} finally {
			running = false;
		}
	}

	/**
	 * Is the server running after start() has been called?
	 */
	public synchronized boolean isRunning() {
		return this.running;
	}

	/**
	 * Returns the bind address. Null means all interfaces.
	 */
	public InetAddress getBindAddress() {
		return this.bindAddress;
	}

	/**
	 * Sets the bind address. Null means all interfaces.
	 */
	public void setBindAddress(InetAddress bindAddress) {
		this.bindAddress = bindAddress;
	}

	/**
	 * Returns the port the server is running on.
	 */
	public int getPort() {
		return this.port;
	}

	/**
	 * Sets the port the server will run on.
	 */
	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * The CommandHandler manages handling the SMTP commands such as QUIT, MAIL,
	 * RCPT, DATA, etc.
	 * 
	 * @return An instance of CommandHandler
	 */
	public CommandHandler getCommandHandler() {
		return this.commandHandler;
	}

	/**
	 * Returns the server configuration.
	 */
	public SMTPServerConfig getConfig() {
		return config;
	}

	public static void main(String[] args) {
		SMTPServer smtpServer = new SMTPServer();
		smtpServer.start();
	}
}
